import wx
import wx.lib.agw.aui as aui
from wx.lib.pubsub import Publisher
from Plugin import PluginManager
from FirebrickWindow import FirebrickWindow
from PreferencesDialog import PreferencesDialog
from HelpWindow import HelpWindow
import firebrick
import logging
import sys
import os

# thanks to Michael Foord and Nicola Larosa for this gem:
# see http://www.voidspace.org.uk/python/configobj.html
from configobj import ConfigObj

import firebrick

class FirebrickApp(wx.App):
    '''An application with plugin support, built upon wx.lib.aui'''
    module = None
    title = "Firebrick"
    window_class = None
    settings = "settings.cfg"

    def __init__(self,  
                 pluginpath=None, settings="settings.cfg"):

        if self.module is None:
            raise Exception("%s: undefined attribute 'module'" % self.__module__)
        if self.window_class is None:
            raise Exception("%s: undefined attribute 'window_class'" % self.__module__)

        wx.App.__init__(self, False)

        self.name = self.module.__name__
        standard_paths = wx.StandardPaths.Get()
        settings_file = os.path.join(standard_paths.GetUserDataDir(), settings)

        self.module.app = self
        firebrick.app = self
        self.log = logging.getLogger(self.name)
#        self.window_class = window_class
        self.SetAppName(self.name)
        if pluginpath is not None:
            self.pluginpath = pluginpath
        else:
            self.pluginpath = [
                os.path.dirname(self.module.__file__),
                os.path.join(os.path.dirname(self.module.__file__), "site-plugins"),
                os.path.join(standard_paths.GetPluginsDir(),"plugins"),
                os.path.join(standard_paths.GetUserDataDir(), "plugins")]
        for dirname in self.pluginpath:
            self.log.info("plugin directory: %s" % dirname)
        self._help_window = None
        self._windows = []
        self._panels = []
        self._editors = []
        self._editor_classes = []
        self._pref_panels = []
        self._pref_dialog = None
        self.perspectives  = []
        self.settings = ConfigObj(settings)
        self._save_settings_timer = None
        self.plugin_manager = PluginManager(self)

        self.plugin_manager.LoadPlugins(self.pluginpath)
        self.plugin_manager.EnablePlugins()

        saved_perspectives = self.GetSettings("perspectives", {})
        for name, info in saved_perspectives.iteritems():
            p = firebrick.Perspective(name, info)
            self.perspectives.append(p)

    def OnExit(self):
        self.plugin_manager.OnExit()
        # some plugins might spawn threads or servers; I don't
        # yet have a gracefull way to tell them to die, so we'll
        # just commit seppuku.
        sys.exit(0)

    def SaveSettings(self, now=False):
        '''Saves settings; may delay unless now=True
        
        Because of the delay, it's safe to call this on every keypress
        in a preferences panel if you so wish. Each time this is called
        the timer is reset, which means there shouldn't be a performance
        hit while the user is actively typing.
        '''
        delay = 3000
        if now:
            self.log.debug("writing settings to disk")
            self.settings.write()
        else:
            if (not self._save_settings_timer):
                self._save_settings_timer= wx.CallLater(delay, self.SaveSettings, now=True)
            else:
                self._save_settings_timer.Restart(delay)
        
    def GetHelpWindow(self):
        if self._help_window is None or not self._help_window:
            self._help_window = HelpWindow(self._windows[0])
        return self._help_window

    def GetPlugin(self, _id):
        '''This returns an instance of a plugin if it has been enabled'''
        for plugin in self.plugin_manager.plugins:
            if plugin._id == _id:
                return plugin
        return None

    def GetPlugins(self):
        '''Return a list of all loaded and enabled plugins'''
        return self.plugin_manager.plugins

    def GetSetting(self, spec, default=None):
        '''Return a setting; spec is _id.name (eg: rwb.core.port)'''
        category, name = spec.rsplit(".", 1)
        if not self.settings.has_key(category):
            self.log.warn("'%s': no such category in settings" % category)
            return default
        if not self.settings[category].has_key(name):
            self.log.warn("'%s': invalid setting" % spec)
            return default
        return self.settings[category][name]

    def GetSettings(self, category, default_settings = None):
        '''Return the settings for a given category

        A category is typically a plugin id (eg: rwb.core) but
        can potentially be any arbitrary string.
        '''
        # I *think* this is working but haven't done extensive testing...

        if default_settings is not None and len(default_settings) > 0:
            # the configobj merge() method works backwards to how I 
            # want it to work. So create a dummy object, merge it with
            # the existing config data, then copy the config data for
            # this id back to the actual object
            tmp = ConfigObj({category: default_settings})
            tmp.merge(self.settings)
            self.settings[category] = tmp[category]
        return self.settings.setdefault(category, {})

    def ShowPreferencesDialog(self, parent):
        '''Show the preferences dialog'''
        if self._pref_dialog is None:
            title = "Preferences - %s" % self.title
            panels = self.GetPreferencesPanels()
            self._pref_dialog = PreferencesDialog(parent, title, panels)
        self._pref_dialog.Show()

    def GetPreferencesPanels(self):
        '''Return a list of preference panels'''
        return self._pref_panels

    def GetToolbar(self, window, _id, create=True):
        '''Return a toolbar instance for the given toolbar id

        If no instance is found and 'create' is True,
        create a new instance. Otherwise return None
        '''
        toolbar = None
        toolbar_class = self.GetToolbarClass(_id)
        if toolbar_class is None:
            self.log.error("Unable to find toolbar class for toolbar id '%s'" % _id)
            return None
        for child in window.GetChildren():
            if isinstance(child, toolbar_class):
                toolbar = child
                break
        if toolbar is None and create:
            self.log.debug("creating new instance of '%s'"% toolbar_class.name)
            toolbar = toolbar_class(window)
        return toolbar

    def GetToolbarClass(self, _id):
        '''Return the constructor for a given toolbar class'''
        for toolbar in self._panels:
            if toolbar._id == _id:
                return toolbar
        self.log.warning("GetToolbarClass: no toolbar found for '%s'" % _id)
        return None

    def GetEditor(self, obj):
        for editor_class in self.editor_classes:
            if editor_class.supports(obj):
                return 

    def GetPanel(self, window, _id, create=True):
        '''Return an instance of the panel given by the panel id

        If create is True, create the panel if it doesn't exist.
        '''
        panel_class = self.GetPanelClass(_id)
        if panel_class is None:
            self.log.error("Unable to find panel class for panel id '%s'" % _id)
            return None
        window = window.GetTopLevelParent()
        key = (window,_id)
        panel = None
        if key not in firebrick.panels and create:
            self.log.debug("creating new instance of '%s'"% panel_class.name)
            panel = panel_class(window)
        # panels contains weak references; want to return actual object
        if key in firebrick.panels:
            return firebrick.panels[key]()
        return None

    def GetPanelClass(self, _id):
        '''Return the constructor for a given panel class'''
        for view in self._panels:
            if view._id == _id:
                return view
        return None

    def Register(self, obj):
        '''Registers an object (panel, toolbar, etc) with the application'''
        if (issubclass(obj, firebrick.BasePanel) or
            issubclass(obj, aui.AuiToolBar)):
            self._panels.append(obj)
        elif issubclass(obj, firebrick.PreferencesPanel):
            self._pref_panels.append(obj)
        elif issubclass(obj, firebrick.Editor):
            self._editors.append(obj)
        else:
            # probably ought to log this...
            import inspect
            print "unknown item being registered:", obj

    def Unregister(self, obj):
        '''Reverses the effets of the Register command

        FIXME: this should also remove any associated panels,
        toolbars, etc from the UI
        '''
        if obj in self._panels:
            self._panels.remove(obj)
        if obj in self._pref_panels:
            self._pref_panels.remove(obj)

    def NewWindow(self, title=None):
        '''Create a new window'''
        if title is None:
            title = self.title
        window = self.window_class(None, title=title, size=(800,600))
        window.Show()
        self._windows.append(window)
        return window

    def GetWindows(self):
        return self._windows

    def Publish(self, topic, **kwargs):
        '''Publish a message

        "topic" is the key that other clients can subscribe to.  All
        other keyword arguments are sent as the message payload.

        '''
        Publisher().sendMessage(topic, kwargs)

    def Subscribe(self, topic, callback):
        '''Call 'callback' when the given topic is published'''
        Publisher().subscribe(callback, topic)

if __name__ == "__main__":
    '''I don't think this works anymore...'''
    handler = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter("%(levelname)s: %(module)s.%(funcName)s: %(message)s")
    handler.setFormatter(formatter)
    logger = logging.getLogger("firebrick")
    logger.addHandler(handler)

    app = firebrick.FirebrickApp(
        module = firebrick,
        settings = "settings.cfg",
        title = "Firebrick Framework",
        pluginpath = [os.path.dirname(__file__)],
        window_class = firebrick.FirebrickWindow,
        )

    app.log = logger
    logger.setLevel(logging.INFO)
    logger.warn("I'm warning you!")
    logger.info("I'm info'ing you!")
    window = app.NewWindow()
    for plugin in app.GetPlugins():
        print plugin.name, plugin._id

    for _id in sys.argv[1:]:
        panel = app.GetPanel(window, _id)
        window.auimgr.GetPane(panel).Show()
    window.auimgr.Update()

    app.MainLoop()
    
